package edu.bu.cs511.p5;

import java.util.LinkedList;
import java.util.ListIterator;

/**
 * WARNING: This scheduler is depricated and should not be used.  It is included
 * only as an example of another way to implement a Scheduler.
 * <p/>
 *
 * RRScheduler A round robin scheduler.  See RRScheduler for a current
 * and up-to-date round robin scheduler.
 *
 * <p/>
 * Can remove the synchonized from the methods and attach it to datastructures
 * if there are scalability issues.
 * <p/>
 * 2/16/06, Gabriel Parmer: basic structure 2/22/06, Gabriel Parmer: Changed to
 * Linked lists and added concurrency
 */
public class OldRRScheduler { //implements IScheduler {
    /* the maximum concurrency allowed to this scheduler, default 1000 */
    private int maxConcurrency = 1000;

    /**
     * We must keep track of not only the Tasks in the runqueue, but also the
     * concurrency level of each task.
     */
    private class AnnotatedNode {
        public Task task;
        /* might use for priority too */
        public int concurrency, allocatedConcurrency;

        public AnnotatedNode(Task t, int concurr) {
            task = t;

            concurrency = concurr;
            allocatedConcurrency = 0;
        }
    }

    /* queue of tasks that are READY to execute */
    private LinkedList<AnnotatedNode> runQueue;
    /* Tasks that are currently running and have expended concurrency */
    private LinkedList<AnnotatedNode> currentlyRunning;

    /**
     * Tasks that cannot run now (are not enabled).  concurrency for these tasks
     * should be 0, thus why it is not in an annotated node.
     */
    private LinkedList<Task> waitingList;

    /**
     * Constructor
     * <p/>
     * @REQUIRES none
     */
    public OldRRScheduler() {
        runQueue = new LinkedList<AnnotatedNode>();
        currentlyRunning = new LinkedList<AnnotatedNode>();
        waitingList = new LinkedList<Task>();
    }

    /**
     * Constructor
     * <p/>
     * @REQUIRES maxConcurr is the maximum value for the concurrency
     * supported by this scheduler, or the maximum number of threads it will
     * pass to children.
     */
    public OldRRScheduler(int maxConcurr) {
        this();
        maxConcurrency = maxConcurr;
    }

    /**
     * @MODIFIES add the task t to the waitingList of this scheduler.
     * <p/>
     * @REQUIRES t is a Task that is not a ThreadManager
     *
     */
    synchronized public boolean addTask(Task t) {
        //AnnotatedNode an = new AnnotatedNode(t, 1);

        //current.add(an);

        return waitingList.add(t);
    }

    /**
     * @MODIFIES
     *
     * @param t
     * @param s
     */
    public boolean addTask(Task t, String s) {
        return addTask(t);
    }

    /**
     * @REQUIRES task t must be in either the runQueue or the waitingList
     * for this scheduler.
     * <p/>
     * @MODIFIES remove the task t from the runQueue or the waitingList.
     *
     * @param t 
     */
    synchronized public void removeTask(Task t) {
        int concur; // FIXME: unused

        if (removeFromRunQueue(t) != -1) return;
        if (waitingList.remove(t)) return;

        // zg: I think there is a bug in the following statement because currentlyRunning
        // contains AnnotatedNode objects and not Task objects
        if (currentlyRunning.remove(t)) return;

        System.out.println("BUG in edu.bu.cs511.p5.OldRRScheduler: attempted to remove task not in taskQueues.");
    }

    /**
     * Round robin does not need an initString.
     *
     * @param 
     */
    public void initString(String s) {
    }

    /**  */
    synchronized public void terminateScheduler() {
        // FIXME: Delete all private data...I should do this right as
        // this is a memory leak.
        runQueue = null;
        waitingList = null;
        currentlyRunning = null;
    }

    /**
     * @REQUIRES there are tasks in the runQueue.
     * <p/>
     * @MODIFIES Schedule tasks in round-robin order.  Manipulate the task
     * queues.
     * <p/>
     * @EFFECTS return the task that should be run next.  null on logic
     * (not expected to be handled well) error.
     *
     * @return 
     */
    synchronized public Task schedule() {
        AnnotatedNode an;
        int size = runQueue.size(); // FIXME: unused

        if (runQueue.size() == 0) {
            System.out.println("BUG: scheduler asked to schedule when there are no " +
                    "tasks in it's runqueue.");
            return null;
        }

        an = runQueue.getFirst();

        // only if this task has no more concurrency will we put it in
        // the currently running queue.
        an.allocatedConcurrency++;
        if (an.allocatedConcurrency == an.concurrency) {
            // FIXME This might throw an exception in which case we
            // have a bug.  Perhaps kill this scheduler and all its
            // tasks.
            an = runQueue.removeFirst();

            currentlyRunning.add(an);
        }

        return an.task;
    }

    /**
     * @EFFECTS return the concurrency level or -1 if the element doesn't
     * exist in the runQueue.
     * <p/>
     * @MODIFIES removes t from the runQueue if it is on it.
     *
     * @param 
     *
     * @return 
     */
    private int removeFromRunQueue(Task t) {
        // FIXME: I think there is a bug in the following statement because currentlyRunning
        // contains AnnotatedNode objects and not Task objects
        int index = runQueue.indexOf(t);
        AnnotatedNode an;

        if (index == -1) return -1;

        an = runQueue.remove(index);

        return an.concurrency;
    }

    /**
     * @MODIFIES runQueue, adds task at end of queue
     *
     * @param an 
     */
    private void addToRunqueue(AnnotatedNode an) {
        runQueue.addLast(an);
    }

    /**
     * @REQUIRES t must be scheduled by this scheduler and in the
     * currentlyRunning, blocked is true if it cannot compute further at this
     * time.
     * <p/>
     * @MODIFIES if the task t is blocked, take it out of the runqueue and put
     * it in the waitingList.
     *
     * @param t       
     * @param blocked 
     */
    synchronized public void jobFinished(Task t, boolean blocked) {
        AnnotatedNode an = null;
        boolean found = false;
        ListIterator<AnnotatedNode> li = runQueue.listIterator();

        while (li.hasNext()) {
            an = li.next();

            if (an.task == t) {
                found = true;
                break;
            }
        }

        if (found) {
            runQueue.remove(an);
        }
        /* must be in runningQueue */
        else {
            li = currentlyRunning.listIterator();
            while (li.hasNext()) {
                an = li.next();

                if (an.task == t) {
                    found = true;
                    break;
                }
            }

            if (found) {
                currentlyRunning.remove(an);
            } else {
                System.out.println("BUG in edu.bu.cs511.p5.OldRRScheduler: finshed job not in runQueue or currentlyRunning.");
                return;
            }
        }

        an.allocatedConcurrency--;

        if (blocked) {
            waitingList.add(t);
        } else {
            addToRunqueue(an);
        }
    }

    /**
     * @MODIFIES Change the concurrency of child in list to concurrency if child
     * is in list.
     * <p/>
     * @EFFECTS return the sum of the concurrencies of all tasks in the
     * specified list.
     *
     * @param list       
     * @param child      
     * @param concurrency 
     *
     * @return 
     */
    private int updateConcurrency(LinkedList<AnnotatedNode> list, Task child, int concurrency) {
        ListIterator<AnnotatedNode> li = list.listIterator();
        int concurSum = 0;

        while (li.hasNext()) {
            AnnotatedNode an = li.next();

            if (an.task.equals(child)) {
                an.concurrency = concurrency;
            }

            concurSum += an.concurrency;
        }

        return concurSum;
    }

    /**
     * @REQUIRES child might be null, concurrency is the concurrency of the
     * child.
     * <p/>
     * @EFFECTS return the currently observed concurrency which how many
     * opcodes there are in our subgraph.  This should possibly change.
     *
     * @param child       
     * @param concurrency 
     *
     * @return 
     */
    synchronized public int childConcurrencyLevel(Task child, int concurrency) {
        int location; // FIXME: unused
        int concurSum = 0;

        /**
         * If this child is in the waitingList, then remove it and add
         * it to the runQueue.
         */
        if (child != null && waitingList.remove(child)) {
            AnnotatedNode an = new AnnotatedNode(child, concurrency);
            runQueue.add(an);
        }

        /**
         * Update the child's concurrency level and return our new
         * concurrency level.
         */
        concurSum += updateConcurrency(runQueue, child, concurrency);
        concurSum += updateConcurrency(currentlyRunning, child, concurrency);

        return Math.min(concurSum, maxConcurrency);
    }

    /**
     * Simply for use to recalculate concurrency level, not update for a child.
     *
     * @return 
     */
    public int childConcurrencyLevel() {
        return childConcurrencyLevel(null, 0);
    }
}
